%%% Game.oz
%
% This module implements the minesweeper game.
%
% {Game.new NR#NC BinField ?SymField ?Play}
%    creates a game from binary field BinField of size NR#NC
%
% {Game.status SymField Nmines}
%    returns a status stream for SymField (still N squares to uncover...)
%
% Game.switchable
%    this class has a trigger to switch on/off an object's action
%
% Game.autoplay
%    this class plays all squares known as safe(_)
%
% Game.zeroProp
%    this class marks the neighbors of a safe(0) square with safe(_)
%
functor
import
   Field at 'Field.ozf'

export
   new: NewGame
   Status
   Switchable
   Autoplay
   ZeroProp

define
   % creates a game from binary field BinField of size NR#NC
   proc {NewGame NR#NC BinField ?SymField ?Play}
      BoxI = {Field.coord NR#NC}.boxI
      Info = fun {$ I}
		for J in {BoxI I 1} sum:S do {S BinField.J} end
	     end
   in
      SymField = {Record.clone BinField}
      proc {Play I}
	 case BinField.I
	 of 0 then SymField.I = safe({Info I})
	 [] 1 then SymField.I = mine(exploded)
	 end
      end
   end

   % returns a status stream for SymField (still N squares to uncover...)
   fun {Status SymField Nmines}
      Events
      P = {NewPort Events}
      N = {Width SymField} - Nmines
   in
      {Record.forAll SymField
       proc {$ X}
	  thread % the message is sent only when the square is played
	     {Send P case X of safe(_) then ok
		     [] mine(exploded) then bang end}
	  end
       end}
      still(N) | thread {MakeStatus Events N} end
   end

   fun {MakeStatus Str N}
      case Str
      of ok|Str andthen N>1 then still(N-1)|{MakeStatus Str N-1}
      [] ok|_   then [won]
      [] bang|_ then [lost]
      end
   end

   % An Interruptable object can be enabled/disabled at any time.
   % This is used to "suspend" the inference objects.
   class Switchable
      attr enabled: unit
      meth enable(B <= true)
	 if B then @enabled = unit else unit = enabled := _ end
      end
   end

   % An Autoplay object play squares that are known to be safe
   class Autoplay from Switchable
      meth init(SymField Play)
	 for I in 1..{Width SymField} do
	    thread
	       case SymField.I of safe(_) then
		  {Wait @enabled} {Play I}
	       else skip end
	    end
	 end
      end
   end

   % If a square is safe(0), then all its neighbors are bound to safe(_)
   class ZeroProp from Switchable
      meth init(SymField nrows:NR ncols:NC)
	 BoxI = {Field.coord NR#NC}.boxI
      in
	 for I in 1..NR*NC do
	    thread
	       case SymField.I of safe(0) then
		  {Wait @enabled}
		  for J in {BoxI I 1} do SymField.J = safe(_) end
	       else skip end
	    end
	 end
      end
   end
end
